﻿// -----------------------------------------------------------------------
// <copyright file="DbRule.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Centido.Core
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Text.RegularExpressions;

    /// <summary>
    ///   The database rules.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Db")]
    public static class DbRule
    {
        /// <summary>
        ///   The marker of the code in error messages.
        /// </summary>
        public const string ErrorCodeMarker = "#error_code";

        /// <summary>
        ///   The maximum length of identifiers.
        /// </summary>
        /// <remarks>
        ///   The actual value of the maximum length of identifiers constant
        ///   is set because of Oracle limitations.
        /// </remarks>
        public const int IdentifierMaxLength = 26;


        /// <summary>
        ///   Cached keywords.
        /// </summary>
        private static List<string> _keywords;

        /// <summary>
        ///   Gets the database keywords.
        /// </summary>
        public static IEnumerable<string> Keywords
        {
            get
            {
                yield return "access";
                yield return "add";
                yield return "all";
                yield return "alter";
                yield return "and";
                yield return "any";
                yield return "as";
                yield return "asc";
                yield return "audit";
                //Sql server
                yield return "authorization";
                //Sql server
                yield return "backup";
                //Sql server
                yield return "begin";
                yield return "between";
                //Sql server
                yield return "break";
                //Sql server
                yield return "browse";
                //Sql server
                yield return "bulk";
                yield return "by";
                //Sql server
                yield return "cascade";
                //Sql server
                yield return "case";
                yield return "char";
                yield return "check";
                //Sql server
                yield return "checkpoint";
                //Sql server
                yield return "close";
                yield return "cluster";
                //Sql server
                yield return "clustered";
                //Sql server
                yield return "coalesce";
                //Sql server
                yield return "collate";
                yield return "column";
                yield return "comment";
                //Sql server
                yield return "commit";
                yield return "compress";
                //Sql server
                yield return "compute";
                yield return "connect";
                //Sql server
                yield return "constraint";
                //Sql server
                yield return "contains";
                //Sql server
                yield return "containstable";
                //Sql server
                yield return "continue";
                //Sql server
                yield return "convert";
                yield return "create";
                //Sql server
                yield return "cross";
                yield return "current";
                //Sql server
                yield return "current_date";
                //Sql server
                yield return "current_time";
                //Sql server
                yield return "current_timestamp";
                //Sql server
                yield return "current_user";
                //Sql server
                yield return "cursor";
                //Sql server
                yield return "database";
                yield return "date";
                //Sql server
                yield return "dbcc";
                //Sql server
                yield return "deallocate";
                yield return "decimal";
                //Sql server
                yield return "declare";
                yield return "default";
                yield return "delete";
                //Sql server
                yield return "deny";
                yield return "desc";
                //Sql server
                yield return "disk";
                yield return "distinct";
                //Sql server
                yield return "distributed";
                //Sql server
                yield return "double";
                yield return "drop";
                //Sql server
                yield return "dump";
                yield return "else";
                //Sql server
                yield return "end";
                //Sql server
                yield return "errlvl";
                //Sql server
                yield return "escape";
                //Sql server
                yield return "except";
                yield return "exclusive";
                //Sql server
                yield return "exec";
                //Sql server
                yield return "execute";
                yield return "exists";
                //Sql server
                yield return "exit";
                //Sql server
                yield return "external";
                //Sql server
                yield return "fetch";
                yield return "file";
                //Sql server
                yield return "fillfactor";
                yield return "float";
                yield return "for";
                //Sql server
                yield return "foreign";
                //Sql server
                yield return "freetext";
                //Sql server
                yield return "freetexttable";
                yield return "from";
                //Sql server
                yield return "full";
                //Sql server
                yield return "function";
                //Sql server
                yield return "goto";
                yield return "grant";
                yield return "group";
                yield return "having";
                //Sql server
                yield return "holdlock";
                yield return "identified";
                //Sql server
                yield return "identity";
                //Sql server
                yield return "identity_insert";
                //Sql server
                yield return "identitycol";
                //Sql server
                yield return "if";
                yield return "immediate";
                yield return "in";
                yield return "increment";
                //Sql server
                yield return "inner";
                yield return "index";
                yield return "initial";
                yield return "insert";
                yield return "integer";
                yield return "intersect";
                yield return "into";
                yield return "is";
                //Sql server
                yield return "join";
                //Sql server
                yield return "key";
                //Sql server
                yield return "kill";
                //Sql server
                yield return "left";
                yield return "level";
                yield return "like";
                //Sql server
                yield return "lineno";
                //Sql server
                yield return "load";
                yield return "lock";
                yield return "long";
                yield return "maxextents";
                //Sql server
                yield return "merge";
                yield return "minus";
                yield return "mlslabel";
                yield return "mode";
                yield return "modify";
                //Sql server
                yield return "national";
                yield return "noaudit";
                //Sql server
                yield return "nocheck";
                yield return "nocompress";
                //Sql server
                yield return "nonclustered";
                yield return "not";
                yield return "nowait";
                yield return "null";
                //Sql server
                yield return "nullif";
                yield return "number";
                yield return "of";
                //Sql server
                yield return "off";
                yield return "offline";
                //Sql server
                yield return "offsets";
                yield return "on";
                yield return "online";
                //Sql server
                yield return "open";
                //Sql server
                yield return "opendatasource";
                //Sql server
                yield return "openquery";
                //Sql server
                yield return "openrowset";
                //Sql server
                yield return "openxml";
                yield return "option";
                yield return "or";
                yield return "order";
                //Sql server
                yield return "outer";
                //Sql server
                yield return "over";
                yield return "overflow";
                yield return "pctfree";
                //Sql server
                yield return "percent";
                //Sql server
                yield return "pivot";
                //Sql server
                yield return "plan";
                //Precision is allowed in Sql Server!!!
                //Sql server
                yield return "primary";
                //Sql server
                yield return "print";
                yield return "prior";
                yield return "privileges";
                //Sql server
                yield return "proc";
                //Sql server
                yield return "procedure";
                yield return "public";
                //Sql server
                yield return "raiserror";
                yield return "raw";
                //Sql server
                yield return "read";
                //Sql server
                yield return "readtext";
                //Sql server
                yield return "reconfigure";
                //Sql server
                yield return "references";
                yield return "rename";
                //Sql server
                yield return "replication";
                yield return "resource";
                //Sql server
                yield return "restore";
                //Sql server
                yield return "restrict";
                //Sql server
                yield return "return";
                //Sql server
                yield return "revert";
                //Sql server
                yield return "revoke";
                //Sql server
                yield return "right";
                //Sql server
                yield return "rollback";
                yield return "row";
                //Sql server
                yield return "rowcount";
                yield return "rowid";
                yield return "rownum";
                yield return "rows";
                //Sql server
                yield return "rule";
                //Sql server
                yield return "save";
                //Sql server
                yield return "schema";
                //Sql server
                yield return "securityaudit";
                yield return "select";
                yield return "session";
                //Sql server
                yield return "session_user";
                yield return "set";
                //Sql server
                yield return "setuser";
                yield return "share";
                //Sql server
                yield return "shutdown";
                yield return "size";
                yield return "smallint";
                //Sql server
                yield return "some";
                //Sql server
                yield return "statistics";
                yield return "start";
                yield return "successful";
                yield return "synonym";
                yield return "sysdate";
                //Sql server
                yield return "system_user";
                yield return "table";
                //Sql server
                yield return "Sample";
                //Sql server
                yield return "textsize";
                yield return "then";
                yield return "to";
                //Sql server
                yield return "top";
                //Sql server
                yield return "tran";
                //Sql server
                yield return "transaction";
                yield return "trigger";
                //Sql server
                yield return "truncate";
                //Sql server
                yield return "tsequal";
                yield return "uid";
                yield return "union";
                yield return "unique";
                //Sql server
                yield return "unpivot";
                yield return "update";
                //Sql server
                yield return "updatetext";
                //Sql server
                yield return "use";
                yield return "user";
                yield return "validate";
                yield return "values";
                yield return "varchar";
                yield return "varchar2";
                //Sql server
                yield return "varying";
                yield return "view";
                //Sql server
                yield return "waitfor";
                //Sql server
                yield return "when";
                yield return "whenever";
                yield return "where";
                //Sql server
                yield return "while";
                yield return "with";
                //Sql server
                yield return "writetext";
            }
        }

        /// <summary>
        ///   Gets the allowed special symbols.
        /// </summary>
        public static IEnumerable<char> AllowedSpecialSymbols
        {
            get
            {
                yield return '#';
                yield return '$';
                yield return '_';
            }
        }

        /// <summary>
        ///   Checks that the specified value is a keyword.
        /// </summary>
        /// <param name = "value">The specified value.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="value"/> is null.</exception>
        /// <returns>Returns true if the specified value is a keyword.</returns>
        public static bool IsKeyword(string value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            if (_keywords == null)
            {
                _keywords = new List<string>();
                _keywords.AddRange(Keywords);
            }

            return _keywords.Contains(value.ToLower());
        }

        /// <summary>
        ///   Checks the value is a valid indentifier
        /// </summary>
        /// <param name = "value">The value</param>
        public static void TestIdentifier(string value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            if (value.Trim().Length == 0)
                throw new ArgumentException("Identifier cannot be empty.");

            value = value.Trim();

            // The name is too long
            if (value.Length > IdentifierMaxLength)
                throw new ArgumentException("The length of the identifier is too long.");

            // If identifier is reserved
            if (IsKeyword(value))
                throw new ArgumentException(
                    "This name is a keyword in one of the supported dbms and cannot be used as an identifier.");

            // If the name doesn't start with the letter
            if (!char.IsLetter(value[0]))
                throw new ArgumentException("The identifier must start with a letter.");

            // If the name contains illegal symbols
            if (value.Any(ch => !char.IsLetterOrDigit(ch) && !AllowedSpecialSymbols.Contains(ch)))
                throw new ArgumentException("The identifier name must not contain forbidden symbols.");

            // If the name contains non-english letters
            if (!Regex.IsMatch(value, "^[a-zA-Z0-9_]+$"))
                throw new ArgumentException("The identifier name must not contain non-english letters.");
        }


        /// <summary>
        ///   Gets the code of the specified exception.
        /// </summary>
        /// <param name = "exception">The database exception.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="exception"/> is null.</exception>
        /// <returns>Returns the error code of the specified exception.</returns>
        public static DbErrorCode GetErrorCode(this Exception exception)
        {
            if (exception == null)
                throw new ArgumentNullException("exception");

            if (string.IsNullOrEmpty(exception.Message))
                return DbErrorCode.None;

            return GetErrorCode(exception.Message);
        }

        /// <summary>
        ///   Gets the code of the specified exception message.
        /// </summary>
        /// <param name = "errorMessage">The database exception message.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="errorMessage"/> is null.</exception>
        /// <returns>Returns the error code of the specified exception.</returns>
        public static DbErrorCode GetErrorCode(string errorMessage)
        {
            if (errorMessage == null)
                throw new ArgumentNullException("errorMessage");

            var match = Regex.Match(
                errorMessage, string.Format(@"{0}\s*=\s*\d+", ErrorCodeMarker), RegexOptions.IgnoreCase);

            if (match.Success)
            {
                var errorCodeValue = int.Parse(match.Value.Split('=')[1].Trim());

                if (Enum.IsDefined(typeof(DbErrorCode), (DbErrorCode)errorCodeValue))
                    return (DbErrorCode)errorCodeValue;
                return DbErrorCode.None;
            }

            return DbErrorCode.None;
        }
    }
}